package network;

import com.thoughtworks.xstream.XStream;
import da.*;
import data.Building;
import data.BuildingType;
import data.Resource;
import data.Village;
import data.User;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Class ClientHandler.java
 * 
 * Responsible for the communication between the server and 1 particular client.
 * 
 * @author Stijn Verslycken, Jonas Van Den Bossche
 * @date 2007-2008
 */
public class ClientHandler implements Runnable {

    private Socket clientLink;
    private ObjectInputStream in;
    private ObjectOutputStream out;
    private MaiaServer server;
    private int id;
    private boolean running;
    private User user;

    public int getId() {
        return id;
    }

    public boolean isRunning() {
        return running;
    }

    public void setRunning(boolean running) {
        this.running = running;
    }

    public User getUser() {
        return user;
    }

    private void setUser(User user) {
        this.user = user;
    }

    public ClientHandler(Socket clientLink, MaiaServer server, int id) {
        this.clientLink = clientLink;
        this.server = server;
        this.id = id;

        // -1 indicates unknown user
        this.user = new User(-1, "unknown", "unknown", "unknown", "unknown", "unknown", "unknown", false);
        setRunning(true);

        try {
            out = new ObjectOutputStream(clientLink.getOutputStream());
            in = new ObjectInputStream(clientLink.getInputStream());
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }

    public void run() {
        while (running) {
            processClient();
        }
    }

    /*
     * Processes client requests.
     * 1. Commando is read.
     * 2. Selects the appropriate method.
     */
    private void processClient() {
        try {
            // read command
            String incomming = (String) in.readObject();
            String command = incomming.substring(0, incomming.indexOf("#", 2) + 1);
            final String params = incomming.substring(incomming.indexOf("#", 2) + 1);




            // interpret command and pass through to the appropriate method.
            if (command.equals("#LOGIN#")) {
                loginUser(params);
            } else if (command.equals("#WORLDIN#")) {
                loginToMaiaWorld(params);
            } else if (command.equals("#CONNTERM#")) {
                closeConnection();
            } else if (command.equals("#CHECKAVAILUSR#")) {
                checkAvailabilityUsername(params);
            } else if (command.equals("#CHECKAVAILVILL#")) {
                checkAvailabilityVillageName(params);
            } else if (command.equals("#USRADD#")) {
                addUser(params);
            } else if (command.equals("#QRYUSERBYID#")) {
                getUserByUserId(params);
            } else if (command.equals("#QRYDORP#")) {
                getVillageOfUser(params);
            } else if (command.equals("#QRYVILLAGES#")) {
                getInitializedVillages();
            } else if (command.equals("#QRYBUILDINGS#")) {
                getBuildings();
            } else if (command.equals("#QRYBUILDINGTYPES#")) {
                getBuildingTypes();
            } else if (command.equals("#UPDATEVILLAGELOCATION#")) {
                updateVillageLocation(params);
            } else if (command.equals("#ASSIGNDEFAULTRESOURCES#")) {
                assignDefaultResources(params);
            } else if (command.equals("#CHATF#")) {
                Thread t = new Thread(new Runnable() {

                    public void run() {
                        sendFriendsChatMessage(params);
                    }
                });
                t.start();

            } else if (command.equals("#CHATT#")) {
                Thread t = new Thread(new Runnable() {

                    public void run() {
                        sendTribeChatMessage(params);
                    }
                });
                t.start();

            } else if (command.equals("#CHATA#")) {
                Thread t = new Thread(new Runnable() {

                    public void run() {
                        sendAllChatMessage(params);
                    }
                });
                t.start();

            } else if (command.equals("#QRYUSRBYNAME#")) {
                searchUser(params);
            } else if (command.equals("#ADDVILLAGE#")) {
                addVillage(params);
            } else if (command.equals("#QRYONLINEUSERS#")) {
                Thread t = new Thread(new Runnable() {

                    public void run() {
                        try {
                            sendOnlineWorldUsers();
                        } catch (IOException ex) {
                            Logger.getLogger(ClientHandler.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                });
                t.start();
            } else if (command.equals("#QRYFRIENDS#")) {
                Thread t = new Thread(new Runnable() {

                    public void run() {
                        try {
                            sendFriends();
                        } catch (IOException ex) {
                            Logger.getLogger(ClientHandler.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                });
                t.start();
            } else if (command.equals("#QRYTRIBEMEMBERS#")) {
                Thread t = new Thread(new Runnable() {

                    public void run() {
                        try {
                            sendTribeMembers();
                        } catch (IOException ex) {
                            Logger.getLogger(ClientHandler.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                });
                t.start();
            } else if (command.equals("#CHATLISTS#")) {
                Thread t = new Thread(new Runnable() {

                    public void run() {
                        try {
                            sendOnlineWorldUsers();
                            sendFriends();
                            sendTribeMembers();
                        } catch (IOException ex) {
                            Logger.getLogger(ClientHandler.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                });
                t.start();
            } else if (command.equals("#QRYRESOURCES#")) {
                Thread t = new Thread(new Runnable() {

                    public void run() {
                        try {
                            sendResources();
                        } catch (IOException ex) {
                            Logger.getLogger(ClientHandler.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                });
                t.start();
            } else if (command.equals("#ADDBUILDING#")) {
                Thread t = new Thread(new Runnable() {

                    public void run() {
                        addBuilding(params);
                    }
                });
                t.start();
            } else if (command.equals("#REMOVEBUILDING#")) {
                Thread t = new Thread(new Runnable() {

                    public void run() {
                        removeBuilding(params);
                    }
                });
                t.start();
            } else if (command.equals("#UPDATEBUILDING#")) {
                Thread t = new Thread(new Runnable() {

                    public void run() {
                        updateBuilding(params);
                    }
                });
                t.start();
            } else if (command.equals("#UPDATERESOURCES#")) {
                Thread t = new Thread(new Runnable() {

                    public void run() {
                        updateResources(params);
                    }
                });
                t.start();
            }



        } catch (IOException ex) {
            System.out.println(ex.getMessage());
            closeConnection();
        } catch (ClassNotFoundException ex) {
            System.out.println(ex.getMessage());
            closeConnection();
        }
    }

    /*
     * Checks whether a username is already taken or not.
     * @param username
     */
    private void checkAvailabilityUsername(String username) throws IOException {
        boolean isFree = false;

        User usr = UserDA.getInstance().getUserByName(username);
        if (usr == null) {
            isFree = true;
        }

        out.writeBoolean(isFree);
        out.flush();
    }

    /*
     * Checks whether a village name is already taken or not.
     * @param villageName
     */
    private void checkAvailabilityVillageName(String villageName) throws IOException {
        boolean isFree = false;

        Village vill = VillageDA.getInstance().getVillageByName(villageName);
        if (vill == null) {
            isFree = true;
        }

        out.writeBoolean(isFree);
        out.flush();
    }

    /*
     * Adds a village with default settings (no location!) to the DB.
     * 
     * In: String villageName
     * Out: /
     */
    private void addVillage(String xml) throws ClassNotFoundException {

        XStream xstream = new XStream();
        Village village = (Village) xstream.fromXML(xml);
        VillageDA.getInstance().addVillage(village);
    }

    private void updateVillageLocation(String params) {
        int villageId = Integer.parseInt(params.split(";")[0]);
        int xpos = Integer.parseInt(params.split(";")[1]);
        int ypos = Integer.parseInt(params.split(";")[2]);

        VillageDA.getInstance().updateVillageLocation(villageId, xpos, ypos);
    }

    private void assignDefaultResources(String params) {
        int villageId = Integer.parseInt(params.split(";")[0]);

        Resource defaultFoodResource = new Resource(villageId, "Food", 250);
        ResourceDA.getInstance().addResource(defaultFoodResource);

        Resource defaultWoodResource = new Resource(villageId, "Wood", 500);
        ResourceDA.getInstance().addResource(defaultWoodResource);
        
        Resource defaultIronResource = new Resource(villageId, "Iron", -1);
        ResourceDA.getInstance().addResource(defaultIronResource);
        
        Resource defaultStoneResource = new Resource(villageId, "Stone", -1);
        ResourceDA.getInstance().addResource(defaultStoneResource);
        
        Resource defaultGoldResource = new Resource(villageId, "Gold", -1);
        ResourceDA.getInstance().addResource(defaultGoldResource);
    }


    /*
     * Fetches a user.
     * In: String username
     * Out: String XML-stream of user object
     * 
     */
    private void searchUser(String username) throws IOException {

        XStream xstream = new XStream();

        User usr = UserDA.getInstance().getUserByName(username);

        String xml = xstream.toXML(usr);
        sendToClient("#_QRYUSRBYNAME#" + xml);
    }

    /*
     * Logs in a user, and associates this user with the client handler.
     * 
     * In: String username, String password
     * Out: String XML-stream of user object
     * 
     */
    private void loginUser(String credentials) throws IOException, ClassNotFoundException {

        XStream xstream = new XStream();

        String username = credentials.split(";")[0];
        String password = credentials.split(";")[1];


        if (!server.isAlreadyLoggedIn(username)) {
            User usr = UserDA.getInstance().getUserByUsernamePassword(username, password);

            // associate user with this client handler
            this.setUser(usr);


            String xml = xstream.toXML(usr);
            sendToClient(xml);

        } else {
            String xml = xstream.toXML(null);
            sendToClient(xml);
        }

    }

    /*
     * Logs in a user to Maiaworld: 
     */
    private void loginToMaiaWorld(String params) throws IOException {

        // Associate right user with maia client.
        XStream xstream = new XStream();
        User usr = (User) xstream.fromXML(params);
        this.setUser(usr);


        // Add this client to the server's online client list;
        server.addToWorldClienthandlers(this);

        // Send names of friends an tribe members to the client.
        sendFriends();
        sendTribeMembers();
    }

    private void signOutOfMaiaWorld() {
        // Remove this client to the server's online client list
        Vector<ClientHandler> worldclients = server.getClienthandlers();
        worldclients.remove(this);
        server.setWorldClientHandlers(worldclients);
        server.printNumberOfConnectedWorldClients();

        // Inform the other clients about the online status of this client
        server.updateOnlineWorldUsersStatus();
    }

    /*
     * Adds a user to the DB (registration).
     * 
     * In: String XML-representation of User object
     * Out: /
     * 
     */
    private void addUser(String xml) throws ClassNotFoundException {

        XStream xstream = new XStream();

        User newUser = (User) xstream.fromXML(xml);

        UserDA.getInstance().addUser(newUser);
    }

    /*
     * Fetches the village of a user.
     *
     * In: String username
     * Out: Out: String XML-stream of Village object
     */
    private void getVillageOfUser(String username) throws IOException, ClassNotFoundException {

        XStream xstream = new XStream();

        Village dorp = VillageDA.getInstance().getVillageByUsername(username);

        String xml = xstream.toXML(dorp);
        sendToClient("#_QRYDORP#" + xml);
    }

    /*
     * Fetches all villages on the server 
     *
     * In: /
     * Out: String XML-stream of Vector<Village> villages
     */
    private void getAllVillages() throws IOException, ClassNotFoundException {
        XStream xstream = new XStream();
        Vector<Village> villages = VillageDA.getInstance().getAllVillages();

        String xml = xstream.toXML(villages);
        sendToClient("#_QRYALLVILLAGES#" + xml);
    }

    private void getUserByUserId(String userId) throws IOException {
        XStream xstream = new XStream();
        User usr = UserDA.getInstance().getUserById(Integer.parseInt(userId));

        String xml = xstream.toXML(usr);
        sendToClient("#_QRYUSERBYID#" + xml);
    }

    private void getInitializedVillages() throws IOException {
        XStream xstream = new XStream();
        Vector<Village> villages = VillageDA.getInstance().getAllInitializedVillages();

        String xml = xstream.toXML(villages);
        sendToClient("#_QRYVILLAGES#" + xml);
    }

    private void getBuildings() throws IOException {
        XStream xstream = new XStream();
        Village village = VillageDA.getInstance().getVillageByUsername(user.getUsername());
        Vector<Building> buildings = BuildingDA.getInstance().getAllBuildingsOfVillage(village.getVillageId());

        String xml = xstream.toXML(buildings);
        sendToClient("#_QRYBUILDINGS#" + xml);
    }

    private void getBuildingTypes() throws IOException {
        XStream xstream = new XStream();
        Vector<BuildingType> buildingTypes = BuildingTypeDA.getInstance().getAllBuildingTypes();

        String xml = xstream.toXML(buildingTypes);
        sendToClient("#_QRYBUILDINGTYPES#" + xml);
    }

    private void sendOnlineWorldUsers() throws IOException {
        XStream xstream = new XStream();

        // Get a list with all the users who are playing.
        Vector<User> onlineUsers = server.getOnlineWorldUsers();
        // Remove local user from this list
        onlineUsers.remove(this.getUser());

        String xml = xstream.toXML(onlineUsers);
        sendToClient("#_QRYONLINEUSERS#" + xml);
    }

    private void sendFriends() throws IOException {
        XStream xstream = new XStream();

        Vector<User> friends = UserDA.getInstance().getFriendsOfUser(user.getUsername());

        String xml = xstream.toXML(friends);
        sendToClient("#_QRYFRIENDS#" + xml);
    }

    private void sendResources() throws IOException {

        XStream xstream = new XStream();

        Village village = VillageDA.getInstance().getVillageByUsername(user.getUsername());

        Vector<Resource> resources = ResourceDA.getInstance().getResourcesOfVillage(village.getVillageId());

        String xml = xstream.toXML(resources);
        sendToClient("#_QRYRESOURCES#" + xml);
    }

    private void sendTribeMembers() throws IOException {
        XStream xstream = new XStream();

        // Fetches usernames of the user's friends
        Vector<User> tribeMembers = UserDA.getInstance().getTribeMembersOfUser(user.getUsername());

        String xml = xstream.toXML(tribeMembers);
        sendToClient("#_QRYTRIBEMEMBERS#" + xml);
    }

    protected void updateOnlineWorldUsersStatus() {
        try {
            sendOnlineWorldUsers();
        } catch (IOException ex) {
            Logger.getLogger(ClientHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private synchronized void sendToClient(Object obj) throws IOException {
        out.writeObject(obj);
        out.flush();
    }

    private void addBuilding(String params) {

        XStream xstream = new XStream();
        Building b = (Building) xstream.fromXML(params);

        BuildingDA.getInstance().addBuilding(b);
        try {
            getBuildings();
        } catch (IOException ex) {
            Logger.getLogger(ClientHandler.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    protected void updateResources(Vector<Resource> resources) throws IOException {
        XStream xstream = new XStream();

        String xml = xstream.toXML(resources);
        sendToClient("#_QRYRESOURCES#" + xml);
    }

    private void removeBuilding(String params) {
        int buildingId = Integer.parseInt(params);
        BuildingDA.getInstance().removeBuilding(buildingId);
    }

    private void updateBuilding(String params) {
        XStream xstream = new XStream();
        Building b = (Building) xstream.fromXML(params);

        BuildingDA.getInstance().editBuilding(b);
    }

    private void updateResources(String params) {
        try {
            XStream xstream = new XStream();
            Vector<Resource> resources = (Vector<Resource>) xstream.fromXML(params);

            for (Resource rs : resources) {
                ResourceDA.getInstance().editResource(rs);
            }

            sendResources();
        } catch (IOException ex) {
            Logger.getLogger(ClientHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    // <editor-fold desc="CHATTEN" defaultstate="collapsed">
    /*
     * Sends out a chat message to all of the user's friends
     * 
     * In: String sender, String message
     * Out: /
     * 
     */
    private void sendFriendsChatMessage(String message) {
        try {
            String sender = user.getUsername();
            server.sendFriendsChatMessage(sender + ": " + message, sender);
        } catch (IOException ex) {
            Logger.getLogger(ClientHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /*
     * Sends out a chat message to all of the user's tribe members 
     * 
     * In: String sender, int tribeId, String message
     * Out: /
     */
    private void sendTribeChatMessage(String message) {
        try {
            String sender = user.getUsername();
            int tribeId = user.getTribeId();

            server.sendTribeChatMessage(sender + ": " + message, tribeId);
        } catch (IOException ex) {
            Logger.getLogger(ClientHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /*
     * Sends out a chat message to all online users.
     * 
     * In: String sender, String message
     * Out: /
     */
    private void sendAllChatMessage(String message) {
        try {
            String sender = user.getUsername();
            server.sendAllChatMessage(sender + ": " + message);
        } catch (IOException ex) {
            Logger.getLogger(ClientHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /*
     * Receives a chat message sent by a friend.
     * 
     * In: /
     * Out: String message
     */
    protected void receiveFriendsChatMessage(String message) throws IOException {
        sendToClient("#_CHATF#" + message);
    }

    /*
     * Receives a chat message sent by a colleage tribe member.
     * 
     * In: /
     * Out: String message
     */
    protected void receiveTribeChatMessage(String message) throws IOException {
        sendToClient("#_CHATT#" + message);
    }

    /*
     * Receives a chat message sent by any online member.
     * 
     * In: /
     * Out: String message 
     */
    protected void receiveAllChatMessage(String message) throws IOException {
        sendToClient("#_CHATA#" + message);
    }

    // </editor-fold>
    /**
     * Closes connection with client.
     */
    public void closeConnection() {
        try {
            if (clientLink != null) {
                // Stop client handler
                this.setRunning(false);

                // close link, reader, writer
                clientLink.close();
                in.close();
                out.close();

                // Remove client handler from list of active clients
                server.removeFromWorldClienthandlers(this);
                server.removeFromClienthandlers(this);


                System.out.println("Connection with client closed.");

            }

        } catch (IOException ex) {
            System.err.println("Error while disconnecting.");
        }
    }
}
